/**
 * @file hx8347.c
 * @brief hx8347 LCD IC Driver
 *
 * @author yemt@witsi.cn
 * @date 2012-07-28
 * @version hx8347.01
 */
#include "dev.h"
#include "gpio.h"
#include "kernel.h"
#include "iodev.h"
#include <string.h>
#include "ioctl.h"
#include "regs.h"
#include "config.h"

#define LCD_BUFFER_LEN 	(CONFIG_LCD_W * CONFIG_LCD_H * 2)

static uint16_t lcd_fb[LCD_BUFFER_LEN >> 1];  /**< LCD Framebuffer */
static lcd_inf_t * lcd_inf;             /**< LCD Inferface */

extern uint16_t lcd_init_cmd[][2];      /**< LCD Initialization Commands */


static int lcd_hx8347_update(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
        uint32_t x1 = x + w - 1;
        uint32_t y1 = y + h - 1;
        uint16_t * p;
        uint32_t cmd_addr = lcd_inf->inf.i80_lcd.i80_addr;
        uint32_t data_addr = cmd_addr + (1 << lcd_inf->inf.i80_lcd.RS_bit);

//	printf("cmd_addr=0x%x,data_addr = 0x%x\n",cmd_addr,data_addr);
	
        if (x < 0 || y < 0 || w <= 0 || h <= 0) return 0;
        if (x > CONFIG_LCD_W - 1 || y > CONFIG_LCD_H - 1) return 0;
        if (x + w > CONFIG_LCD_W) w = CONFIG_LCD_W - x;
        if (y + h > CONFIG_LCD_H) h = CONFIG_LCD_H - y;

	writew(cmd_addr, 0x3);
	writew(data_addr, x&0xff);
	
	writew(cmd_addr, 0x2);
	writew(data_addr, (x >> 8)&0xff);
	
	writew(cmd_addr, 0x7);
	writew(data_addr, y&0xff);
	
	writew(cmd_addr, 0x6);
	writew(data_addr, (y >> 8)&0xff);

	writew(cmd_addr, 0x5);
	writew(data_addr, x1&0xff);
	
	writew(cmd_addr, 0x4);
	writew(data_addr, (x1 >> 8)&0xff);
	
	writew(cmd_addr, 0x9);
	writew(data_addr, y1&0xff);
	writew(cmd_addr, 0x8);
	writew(data_addr, (y1 >> 8)&0xff);

	writew(cmd_addr, 0x22);

        p = lcd_fb + y * CONFIG_LCD_W + x;
	for (y1 = 0; y1 < h; y1 ++) {
	        for (x1 = 0; x1 < w; x1 ++) {
	                writew(data_addr, *(p + x1));
	        }
	        p += CONFIG_LCD_W;
	}
	return 0;
}

static int lcd_hx8347_init(iodev_t * dev)
{
        dev_inf_t * dinf = iodev_get_inf(dev);
        if (dinf == NULL) return -1;

        lcd_inf = (lcd_inf_t *)dinf->priv;

        //LOG("[I80 LCD] IC HX8347 Init\n");
        if (lcd_inf->backlight_ctl) lcd_inf->backlight_ctl(1); /* Backlight ON */

        uint32_t cmd_addr = lcd_inf->inf.i80_lcd.i80_addr;
        uint32_t data_addr = cmd_addr + (1 << lcd_inf->inf.i80_lcd.RS_bit);

//	printf("cmd_addr=0x%x,data_addr = 0x%x\n",cmd_addr,data_addr);
		
        int i = 0;
        uint16_t cmd, data;
	
        while ((cmd = lcd_init_cmd[i][0])) {
                data = lcd_init_cmd[i ++][1];
                if (cmd == 0xFFFF) mdelay(data);
                else {
                        writew(cmd_addr, cmd);
                        if (data != 0xFFFF)
				writew(data_addr, data);
                }
        }
	memset(lcd_fb,0xf8,sizeof(lcd_fb));
	
        lcd_hx8347_update(0, 0, CONFIG_LCD_W, CONFIG_LCD_H);
        return 0;
}
int lcd_hx8347_ioctl(iodev_t *dev, uint32_t cmd, void *arg)
{
        switch (cmd) {
        case IOCTL_LCD_GET_INFO:
                {
                        lcd_info_t * p = (lcd_info_t *)arg;
                        if (p) {
                                p->width = CONFIG_LCD_W;
                                p->height = CONFIG_LCD_H;
                                p->fbuf = (uint8_t*)lcd_fb;
                                p->fbuf_len = LCD_BUFFER_LEN;
                        }
                }
                break;
        case IOCTL_LCD_UPDATE:
                {
                        lcd_block_t * blk = (lcd_block_t *)arg;
                        if (blk) {
                                lcd_hx8347_update(blk->x, blk->y, blk->w, blk->h);
                        }
                        else {
                                lcd_hx8347_update(0, 0, CONFIG_LCD_W, CONFIG_LCD_H);
                        }
                }
                break;
        }
        return 0;
}
static const iodev_t lcd_hx8347 __iodev_entry = {
        .name = "lcd",
        .init = lcd_hx8347_init,
        .read = NULL,
        .write = NULL,
        .ioctl = lcd_hx8347_ioctl,
        .poll = NULL,
};

